package com.charon.auth.config;

import com.charon.auth.filter.ValidateCodeFilter;
import com.charon.auth.handler.AuthFailureHandler;
import com.charon.auth.handler.AuthSuccessHandler;
import com.charon.auth.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.sql.DataSource;

/**
  *@ClassName: SercurityConfig
  *@Description: 浏览器Security配置
  *@Author: Charon [248135724@qq.com]
  *@Date: 2019/1/15 0015 17:24
  *@Version: 1.0
  */

/**
 * 注意：在使用loginPage和loginProcessingUrl的时候，先加上成功回调函数和失败回调函数，我在写下面配置的时候没有加，而且使用了密码加密，
 * 所以导致无论登录成功还是登录失败将看不出来有任何反应，不知道是对还是错，这个问题从今天下午调试到凌晨3点才发现是没有配置回调的。
 */
@Configuration
@EnableWebSecurity
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    /**
     * 登陆配置
     */
    @Autowired
    private SecurityProperties securityProperties;
    /**
     * 登陆成功回调类
     */
    @Autowired
    private AuthSuccessHandler authSuccessHandler;

    /**
     * 登录失败回调类
     */
    @Autowired
    private AuthFailureHandler authFailureHandler;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private DataSource dataSource;

    /**
     * Description: 持久化token（rememberme）的配置bean。
     * <p>Author: Charon [248135724@qq.com]
     * @Date 2019/2/5 22:34
     * @param
     * @return org.springframework.security.web.authentication.rememberme.PersistentTokenRepository
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl tokenRepository=new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        //在启动时自动创建表，只有在表不存在时可以设置为true，否则将抛出异常。
        tokenRepository.setCreateTableOnStartup(false);
        return tokenRepository;
    }

    /**
     * Description: security配置
     * <p>Author: Charon [248135724@qq.com]
     * @Date 2019/2/5 22:36
     * @param http
     * @return void
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        String loginPage=securityProperties.getBrowser().getLoginPage();
        ValidateCodeFilter validateCodeFilter=new ValidateCodeFilter();
        validateCodeFilter.setAuthFailureHandler(authFailureHandler);
        /**
         * 注意：表单登陆如果使用默认的登陆认证过滤器认证，那么前台的form表单的action需要设置为“login”，请求方式为post
         * 默认用户名的input的name属性设置为username，密码的input设置为password。
         * 具体的内容可以在UsernamePasswordAuthenticationFilter中查看。
         * 如果不想使用默认的，可以通过loginProcessingUrl配置。
         * idea全局搜索类的方式是：ctrl+shift+f  然后选scope就可以搜索jar包中的类了，前提是jar包的资源文件下载好了。
         */

        //普通登录
        //http.httpBasic();
        //设置验证码过滤器，在UsernamePasswordAuthenticationFilter过滤器前执行。
        http.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class)
                //表单登录（认证）（配置时form表单的两个Input的name一个是username，一个是password）
                .formLogin()
                //配置登录页面
                //.loginPage("/charon-login.html")
                .loginPage("/user/login")
                //这里配置的是form表单的action的地址
                .loginProcessingUrl("/user/form")
                //设置登录成功时的回调
                .successHandler(authSuccessHandler)
                //设置登录失败时的回调
                .failureHandler(authFailureHandler)
                //授权
                .and()
                .rememberMe()
                    //添加token配置
                    .tokenRepository(persistentTokenRepository())
                    //设置验证码过期时间
                    .tokenValiditySeconds(securityProperties.getBrowser().getRememberMeSeconds())
                    //设置账户密码获取对象
                    .userDetailsService(userDetailsService)
                .and()
                //对请求做授权
                .authorizeRequests()
                //访问该链接时不需要身份认证
                //.antMatchers("/user/login").permitAll()
                .antMatchers("/code/validateCode","/user/login",loginPage).permitAll()
                .antMatchers("/charon-login.html").permitAll()
                //对于任何请求
                .anyRequest()
                //对于任何请求都需要身份认证
                .authenticated()
                .and()
                //关闭伪造跨站攻击防护
                .csrf().disable();

        //最简易的配置
        /*http.formLogin()
                .loginPage("/charon-login.html")
                .loginProcessingUrl("/login")
                .failureUrl("/500.html")
                .successHandler(authSuccessHandler)
                .failureHandler(authFailureHandler)
                .and()
                .authorizeRequests()
                .antMatchers("/charon-login.html").permitAll()
                .anyRequest()
                .authenticated()
                .and()
                .csrf().disable();*/

    }

    //方式一：配置密码不需要加密（如果需要自定义加密方式，写一个类实现PasswordEncoder接口就可以了）
    //该配置会把页面传过来的密码使用下面指定的加密方式进行加密，加密后的密码要和UserDetailsService的
    // loadUserByUsername方法中的返回值的密码相同。
    /* @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }*/


    @Bean
    public  PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    //方式二：配置加密方式（）
    /*@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        super.configure(auth);
        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder()).withUser("user1").password(new BCryptPasswordEncoder().encode("123456")).roles("USER");
    }*/

}
